package cn.iocoder.yudao.module.els.service.codeGenerator;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.els.controller.admin.codeGenerator.vo.CodeGeneratorPageReqVO;
import cn.iocoder.yudao.module.els.controller.admin.codeGenerator.vo.CodeGeneratorSaveReqVO;
import cn.iocoder.yudao.module.els.dal.dataobject.codeGenerator.CodeGeneratorDO;
import cn.iocoder.yudao.module.els.dal.dataobject.codeGeneratorDetail.CodeGeneratorDetailDO;
import cn.iocoder.yudao.module.els.dal.dataobject.codeGeneratorSerial.CodeGeneratorSerialDO;
import cn.iocoder.yudao.module.els.dal.mysql.codeGenerator.CodeGeneratorMapper;
import cn.iocoder.yudao.module.els.dal.mysql.codeGeneratorDetail.CodeGeneratorDetailMapper;
import cn.iocoder.yudao.module.els.dal.mysql.codeGeneratorSerial.CodeGeneratorSerialMapper;
import cn.iocoder.yudao.module.els.dal.redis.CodeGeneratorRedisDao;
import cn.iocoder.yudao.module.els.enums.codeGenerator.GeneratorValueTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.els.enums.ErrorCodeConstants.*;

/**
 * 编码生成器 Service 实现类
 *
 * @author 吕小文
 */
@Service
@Validated
public class CodeGeneratorServiceImpl implements CodeGeneratorService {

    @Resource
    private CodeGeneratorMapper codeGeneratorMapper;
    @Resource
    private CodeGeneratorDetailMapper codeGeneratorDetailMapper;
    @Resource
    private CodeGeneratorSerialMapper codeGeneratorSerialMapper;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private CodeGeneratorRedisDao codeGeneratorRedisDao;

    /**
     * 流水号占位符
     */
    private final static String SERIAL_PLACE_HOLDER = "{serialNo}";
    private final static String SERIAL_LOCK = "serialLock:%s:%s";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createCodeGenerator(CodeGeneratorSaveReqVO createReqVO) {
        //插入前校验
        checkBeforeCreateOrUpdate(createReqVO);

        CodeGeneratorDO codeGenerator = BeanUtils.toBean(createReqVO, CodeGeneratorDO.class);
        codeGeneratorMapper.insert(codeGenerator);

        // 插入子表
        createCodeGeneratorDetailList(codeGenerator.getId(), createReqVO.getCodeGeneratorDetails());
        // 返回
        return codeGenerator.getId();
    }

    private void checkBeforeCreateOrUpdate(CodeGeneratorSaveReqVO reqVO){

        //校验编码类型是否已经存在
        CodeGeneratorDO dbCodeGenerator = codeGeneratorMapper.selectOne(new LambdaQueryWrapper<CodeGeneratorDO>().eq(CodeGeneratorDO::getCodeType,reqVO.getCodeType()).last("limit 1"));
        if (reqVO.getId() ==null && dbCodeGenerator != null){
            throw exception(CODE_GENERATOR_TYPE_IS_EXIST,reqVO.getCodeType());
        }
        //更新校验存在
        if (reqVO.getId() != null){
            if (codeGeneratorMapper.selectById(reqVO.getId()) == null) {
                throw exception(CODE_GENERATOR_NOT_EXISTS);
            }
            if (dbCodeGenerator != null && !dbCodeGenerator.getId().equals(reqVO.getId())){
                throw exception(CODE_GENERATOR_TYPE_IS_EXIST,reqVO.getCodeType());
            }
        }
        //获取子表的数据
        int index = 1;
        int serialLength = 0;
        int codeLength = 0;
        int serialCount = 0;
        for (CodeGeneratorDetailDO item : reqVO.getCodeGeneratorDetails()){
            item.setSeq(index++);
            codeLength = item.getValueLength() + codeLength;
            if ("serial".equals(item.getValueType())){
                serialLength = item.getValueLength();
                serialCount = serialCount + 1;
                item.setValue("");
            }
        }
        if (serialLength == 0){
            throw exception(CODE_GENERATOR_DETAIL_PARAM_LOST,"流水号");
        }
        if (serialCount>1){
            throw exception(CODE_GENERATOR_DETAIL_SERIAL_OVER,"流水号");
        }
        // 插入
        reqVO.setCodeLength(codeLength);
        reqVO.setSerialLength(serialLength);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCodeGenerator(CodeGeneratorSaveReqVO updateReqVO) {
        //更新前校验
        checkBeforeCreateOrUpdate(updateReqVO);
        // 更新
        CodeGeneratorDO updateObj = BeanUtils.toBean(updateReqVO, CodeGeneratorDO.class);
        codeGeneratorMapper.updateById(updateObj);

        // 更新子表
        updateCodeGeneratorDetailList(updateReqVO.getId(), updateReqVO.getCodeGeneratorDetails());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCodeGenerator(Long id) {
        // 校验存在
        validateCodeGeneratorExists(id);
        // 删除
        codeGeneratorMapper.deleteById(id);

        // 删除子表
        deleteCodeGeneratorDetailByGeneratorId(id);
    }

    private void validateCodeGeneratorExists(Long id) {
        if (codeGeneratorMapper.selectById(id) == null) {
            throw exception(CODE_GENERATOR_NOT_EXISTS);
        }
    }

    @Override
    public CodeGeneratorDO getCodeGenerator(Long id) {
        return codeGeneratorMapper.selectById(id);
    }

    @Override
    public PageResult<CodeGeneratorDO> getCodeGeneratorPage(CodeGeneratorPageReqVO pageReqVO) {
        return codeGeneratorMapper.selectPage(pageReqVO);
    }

    // ==================== 子表（编码生成器明细） ====================

    @Override
    public List<CodeGeneratorDetailDO> getCodeGeneratorDetailListByGeneratorId(Long generatorId) {
        return codeGeneratorDetailMapper.selectListByGeneratorId(generatorId);
    }

    private void createCodeGeneratorDetailList(Long generatorId, List<CodeGeneratorDetailDO> list) {
        list.forEach(o -> o.setGeneratorId(generatorId));
        codeGeneratorDetailMapper.insertBatch(list);
    }

    private void updateCodeGeneratorDetailList(Long generatorId, List<CodeGeneratorDetailDO> list) {
        deleteCodeGeneratorDetailByGeneratorId(generatorId);
		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
        createCodeGeneratorDetailList(generatorId, list);
    }

    private void deleteCodeGeneratorDetailByGeneratorId(Long generatorId) {
        codeGeneratorDetailMapper.deleteByGeneratorId(generatorId);
    }

    // ==================== 生成下x个编码 ====================

    /**
     * 生成下一个编码
     * @param codeType 编码类型
     * @param businessObj 业务对象
     * @return
     * @throws InterruptedException 可能抛出的异常
     */
    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public String getNextCode(String codeType, Object businessObj){
        //获取编码头
        CodeGeneratorDO codeGeneratorDO = codeGeneratorMapper.getByCodeType(codeType);
        if (codeGeneratorDO == null){
            throw exception(CODE_GENERATOR_NOT_EXISTS);
        }
        //获取明细
        List<CodeGeneratorDetailDO> detailList = codeGeneratorDetailMapper.selectListByGeneratorId(codeGeneratorDO.getId());
        if (CollectionUtil.isEmpty(detailList)) {
            throw exception(CODE_GENERATOR_DETAIL_NOT_EXISTS);
        }
        //编码列表
        List<String> codeList = new ArrayList<>(16);
        //流水号key值
        StringBuilder serialKey = new StringBuilder();
        //流水号长度
        int serialLength = 0;
        //循环规则
        for (CodeGeneratorDetailDO detail : detailList){
            if (!GeneratorValueTypeEnum.SERIAL.getCode().equals(detail.getValueType())){
                //如果当前规则不是【流水号】，调用getValue()获取对应的值
                String val = getValue(detail,businessObj);
                codeList.add(val);
                //如果当前规则 标记成了 流水号key值，记录到serialKey中
                if (detail.getIsSerialKey() != null && detail.getIsSerialKey() == 1){
                    serialKey.append(val);
                }
            }else{
                //流水号
                serialLength = detail.getValueLength();
                codeList.add(SERIAL_PLACE_HOLDER);
            }
        }

        //流水号
        String serialNo = "";
        if (serialLength > 0){
            //获取流水号
            try {
                serialNo = getSerialNo(codeGeneratorDO, serialKey.toString());
            }catch (Exception e){
                // todo
            }
        }
        //组装最终编码
        StringBuilder code = new StringBuilder();
        for (String s : codeList){
            if (SERIAL_PLACE_HOLDER.equals(s)){
                code.append(serialNo);
            }else{
                code.append(s);
            }
        }
        return code.toString();
    }

    /**
     * 生成下一组编码
     * @param codeType 编码类型
     * @param businessObj 业务对象
     * @return
     * @throws InterruptedException 可能抛出的异常
     */
    @Override
    public List<String> getNextCodes(String codeType, Object businessObj, int size){
        //获取编码头
        CodeGeneratorDO codeGeneratorDO = codeGeneratorMapper.getByCodeType(codeType);
        if (codeGeneratorDO == null){
            throw exception(CODE_GENERATOR_NOT_EXISTS);
        }
        //获取明细
        List<CodeGeneratorDetailDO> detailList = codeGeneratorDetailMapper.selectListByGeneratorId(codeGeneratorDO.getId());
        if (CollectionUtil.isEmpty(detailList)) {
            throw exception(CODE_GENERATOR_DETAIL_NOT_EXISTS);
        }
        //编码列表
        List<String> codeList = new ArrayList<>(16);
        // 获取流水号的key及流水号
        StringBuilder serialKey = new StringBuilder();
        //流水号长度
        int serialLength = 0;
        //循环规则
        for (CodeGeneratorDetailDO detail : detailList) {
            if (!GeneratorValueTypeEnum.SERIAL.getCode().equals(detail.getValueType())) {
                //如果当前规则不是【流水号】，调用getValue()获取对应的值
                String val = getValue(detail, businessObj);
                codeList.add(val);
                if (detail.getIsSerialKey() != null && detail.getIsSerialKey() == 1){
                    //如果当前规则 标记成了 流水号key值，记录到serialKey中
                    serialKey.append(val);
                }
            } else {
                //流水号
                serialLength = detail.getValueLength();
                codeList.add(SERIAL_PLACE_HOLDER);
            }
        }
        List<String> serialNo = null;
        if (serialLength > 0) {
            try {
                serialNo = getSerialBatchNo(codeGeneratorDO, serialKey.toString(), size);
            }catch (Exception e){
                //todo
            }
        }
        List<String> codes = new ArrayList<>();
        StringBuilder code = new StringBuilder();
        while (size > 0) {
            // 组装最终编码
            for (String str : codeList) {
                if (SERIAL_PLACE_HOLDER.equals(str)) {
                    code.append(null == serialNo ? "" : serialNo.get(size - 1));
                } else {
                    code.append(str);
                }
            }
            codes.add(code.toString());
            code.delete(0, code.length());
            size--;
        }
        return codes;
    }

    /**
     * 解析规则值
     */
    private String getValue(CodeGeneratorDetailDO detail,Object businessObj){
        String value = "";
        if (GeneratorValueTypeEnum.FIXED.getCode().equals(detail.getValueType())){
            //固定值直接返回内容
            value = detail.getValue();
        }else if (GeneratorValueTypeEnum.VAR.getCode().equals(detail.getValueType())){
            //变量从业务对象中通过反射获取
            if (businessObj == null){
                value = detail.getVariableName();
            }else{
                value = (String) ReflectUtil.getFieldValue(businessObj,detail.getValue());
            }
        }else if (GeneratorValueTypeEnum.EL_DATE.getCode().equals(detail.getValueType())){
            // 日期表达式转换
            value = DateUtil.format(DateUtil.date(),detail.getValue());
        }else if (GeneratorValueTypeEnum.ENUM.getCode().equals(detail.getValueType())){
            // todo 枚举从数据字典取值
        }
        return value;
    }

    /**
     * 获取流水号
     * @param codeGeneratorDO 编码DO
     * @param serialKey 流水号key值
     * @return 流水号值
     * @throws InterruptedException 可能抛出的异常
     */
    private String getSerialNo(CodeGeneratorDO codeGeneratorDO,String serialKey) throws InterruptedException {
        //tenantKey Key
        String tenantKey = TenantContextHolder.getTenantId() + "_" + codeGeneratorDO.getCodeType();
        //业务Key
        String businessKey = serialKey;
        //加锁
        RLock lock = redissonClient.getLock(codeGeneratorRedisDao.formatKey(SERIAL_LOCK,tenantKey,businessKey));
        //最多等待时间等待10s， 上锁后20s自动释放锁【leaseTime不传则会启动看门狗机制】
        if (lock.tryLock(10,20, TimeUnit.SECONDS)){
            try{
                //生成的流水号
                String serialNo = "";
                //将redis中对应的key值加1
                Long serialNoLongVal = codeGeneratorRedisDao.incrSerialNoLongVal(tenantKey,businessKey,1);
                serialNo = serialNoLongVal + ""; //long转string
                //获取流水号记录
                CodeGeneratorSerialDO serialDO = codeGeneratorSerialMapper.getSerialByKey(codeGeneratorDO.getId(), serialKey);
                if (1L == serialNoLongVal){
                    // ==1 表示该序列号重新开始或redis记录过期了，检查数据库是否已经存在该记录
                    if (serialDO!=null){
                        //如果数据库存在该记录，可以理解为是redis的值过期了，这里需要将数据库的值+1并重新更新缓存
                        serialDO.setSerialValue(serialDO.getSerialValue() + 1);
                        codeGeneratorSerialMapper.updateById(serialDO);
                        //更新缓存【因为上面+1了，所以这里更新缓存要-1再触发redis的inr方法去+1】
                        codeGeneratorRedisDao.incrSerialNoLongVal(tenantKey,businessKey,serialDO.getSerialValue() -1);
                        codeGeneratorRedisDao.expire(tenantKey,businessKey,(24*60*60 + 1000L));
                    }else{
                        serialDO = new CodeGeneratorSerialDO();
                        serialDO.setGeneratorId(codeGeneratorDO.getId());
                        serialDO.setSerialKey(serialKey);
                        serialDO.setSerialValue(1);
                        codeGeneratorSerialMapper.insert(serialDO);
                        //设置该key的过期时间
                        codeGeneratorRedisDao.expire(tenantKey,businessKey,(24*60*60 + 1000L));
                    }
                    serialNo = serialDO.getSerialValue().toString();
                }else{
                    // 不等于1代表redis中存在记录
                    if (serialDO!=null){
                        serialDO.setSerialValue(serialNoLongVal.intValue());
                        codeGeneratorSerialMapper.updateById(serialDO);
                    }else{
                        serialDO = new CodeGeneratorSerialDO();
                        serialDO.setGeneratorId(codeGeneratorDO.getId());
                        serialDO.setSerialKey(serialKey);
                        serialDO.setSerialValue(serialNoLongVal.intValue());
                        codeGeneratorSerialMapper.insert(serialDO);
                    }
                }
                StringBuilder addZero = new StringBuilder();
                for (int i = 0;i<codeGeneratorDO.getSerialLength() - serialNo.length();i++){
                    addZero.append("0");
                }
                return addZero + serialNo;
            } catch (Exception e) {
                e.printStackTrace();
                throw exception(500,"编码生成业务出错了");
            } finally {
                if (lock.isLocked()){
                    if (lock.isHeldByCurrentThread()){
                        lock.unlock();
                    }
                }
            }
        }else{
            throw exception(500,"系统繁忙，请稍后再试");
        }
    }

    /**
     * 获取一批流水号
     * @param codeGeneratorDO 编码DO
     * @param serialKey 流水号key值
     * @param size 获取的个数
     * @return 一组流水号
     * @throws InterruptedException 可能抛出的异常
     */
    private List<String> getSerialBatchNo(CodeGeneratorDO codeGeneratorDO,String serialKey,int size) throws InterruptedException {
        if (size < 1){
            size = 1;
        }
        //tenantKey Key
        String tenantKey = TenantContextHolder.getTenantId() + "_" + codeGeneratorDO.getCodeType();
        //业务Key
        String businessKey = serialKey;
        //加锁
        RLock lock = redissonClient.getLock(codeGeneratorRedisDao.formatKey(SERIAL_LOCK,tenantKey,businessKey));
        //最多等待时间等待10s， 上锁后20s自动释放锁【leaseTime不传则会启动看门狗机制】
        if (lock.tryLock(10,20, TimeUnit.SECONDS)){
            try{
                //将redis中对应的key值加size
                Long endSerialNo = codeGeneratorRedisDao.incrSerialNoLongVal(tenantKey,businessKey,size);
                //获取流水号记录
                CodeGeneratorSerialDO serialDO = codeGeneratorSerialMapper.getSerialByKey(codeGeneratorDO.getId(), serialKey);
                if (size == endSerialNo){
                    // size==endSerialNo 表示第一次存入redis或redis记录过期了，检查数据库是否已经存在该记录
                    if (serialDO!=null){
                        //如果数据库存在该记录，可以理解为是redis的值过期了，这里需要将数据库的值+size并重新更新缓存
                        serialDO.setSerialValue(serialDO.getSerialValue() + size);
                        codeGeneratorSerialMapper.updateById(serialDO);
                        //更新缓存【因为上面+size了，所以这里更新缓存要-size再触发redis的inr方法去+size】
                        codeGeneratorRedisDao.incrSerialNoLongVal(tenantKey,businessKey,serialDO.getSerialValue() - size);
                        codeGeneratorRedisDao.expire(tenantKey,businessKey,(24*60*60 + 1000L));
                    }else{
                        //如果数据库不存在该记录，代表为第一次存入，取多少个编码就存入多少值即可 serialDO.serialValue = size = endSerialNo
                        serialDO = new CodeGeneratorSerialDO();
                        serialDO.setGeneratorId(codeGeneratorDO.getId());
                        serialDO.setSerialKey(serialKey);
                        serialDO.setSerialValue(size);
                        codeGeneratorSerialMapper.insert(serialDO);
                        //设置该key的过期时间
                        codeGeneratorRedisDao.expire(tenantKey,businessKey,(24*60*60 + 1000L));
                    }
                    endSerialNo = Long.parseLong(serialDO.getSerialValue()+"");
                }else{
                    // endSerialNo不等于size代表redis中存在记录
                    if (serialDO!=null){
                        //数据库记录不为空，则用redis的值赋值给数据库
                        serialDO.setSerialValue(endSerialNo.intValue());
                        codeGeneratorSerialMapper.updateById(serialDO);
                    }else{
                        //数据库记录为空，redis不为空，这种情况可以考虑为数据库记录被清掉了，我们这里以redis优先为原则，使用redis的值纠正数据库的值
                        serialDO = new CodeGeneratorSerialDO();
                        serialDO.setGeneratorId(codeGeneratorDO.getId());
                        serialDO.setSerialKey(serialKey);
                        serialDO.setSerialValue(endSerialNo.intValue());
                        codeGeneratorSerialMapper.insert(serialDO);
                    }
                }

                List<String> serialNos = new ArrayList<>();
                // 创建size个
                StringBuilder addZero = new StringBuilder();
                while (size > 0) {
                    String serialNo = endSerialNo.toString();
                    for (int i = 0; i < codeGeneratorDO.getSerialLength() - serialNo.length(); i++) {
                        addZero.append("0");
                    }
                    addZero.append(serialNo);
                    serialNos.add(addZero.toString());
                    addZero.delete(0, addZero.length());
                    endSerialNo--;
                    size--;
                }
                return serialNos;
            } catch (Exception e) {
                e.printStackTrace();
                throw exception(500,"编码生成业务出错了");
            } finally {
                if (lock.isLocked()){
                    if (lock.isHeldByCurrentThread()){
                        lock.unlock();
                    }
                }
            }
        }else{
            throw exception(500,"系统繁忙，请稍后再试");
        }
    }

}